using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using BEPUphysics;
using BEPUphysics.Entities.Prefabs;
using BEPUphysics.Entities;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.CollisionRuleManagement;
using BEPUphysics.BroadPhaseEntries;

namespace Brain.Bepu
{
    public class BepuPhysicsManager : IPhysics3DManager
    {
        public Vector3 Gravity
        {
            get { return Space.ForceUpdater.Gravity; }
            set { Space.ForceUpdater.Gravity = value; }
        }

        public Vector3 Scale
        {
            get;
            set;
        }

        public bool ShouldUpdate { get; set; }

        public Space Space;

        public bool UseDT = true;

        public BepuPhysicsManager()
        {
            Scale = Vector3.One;
            ShouldUpdate = true;

            Space = new Space();
            Space.ForceUpdater.Gravity = new Vector3(0, -9.82f, 0);

            rayCastFilter = this.RayCastFilter;
            rayCastExcludeFilter = this.RayCastExcludeFilder;
            rayCastExcludesFilter = this.RayCastExcludesFilder;
        }

        public void ChangedState(IWorldObject world, WorldObjectPhysics lastState)
        {
            if (world.PhysicsType == WorldObjectPhysics.NoPhysics)
            {
                if (world.PhysicsObject != null &&
                    world.PhysicsObject is ISpaceObject)
                {
                    ISpaceObject space = world.PhysicsObject.Tag as ISpaceObject;
                    this.Space.Remove(space);
                }
            }
            else
            {
                if (world.PhysicsType == WorldObjectPhysics.Box)
                {
                    world.PhysicsObject = new PhysicObject_Box(this.Space);
                    
                }
                else if (world.PhysicsType == WorldObjectPhysics.Capsule)
                {
                }
                else if (world.PhysicsType == WorldObjectPhysics.Sphere)
                {
                    world.PhysicsObject = new PhysicObject_Sphere(this.Space);
                }
                if (world.PhysicsObject != null)
                {
                    world.PhysicsObject.Position = world.Position;
                    world.PhysicsObject.Scale = world.Scale;
                }
            }
        }

        public void Reset()
        {
            if (Space != null)
            {
                Space.Dispose();
            }

            Space = new Space();
            Space.ForceUpdater.Gravity = new Vector3(0, -9.82f, 0);
        }

        public void Update()
        {
            if (!Engine.Instance.Paused)
            {
                if (UseDT)
                {
                    float dt = (float)Engine.Instance.GameTime.ElapsedGameTime.TotalSeconds;
                    Space.Update(dt);
                }
                else
                {
                    Space.Update();
                }
            }
        }

        public SerializationData Data { get; set; }

        public void SerializeData(DefaultSerializationManager manager)
        {
        }

        public void DeserializeData(DefaultSerializationManager manager, SerializationData newData)
        {
        }


        public bool RayTest(Ray ray, out RayTestData hitData)
        {
            RayCastResult result;
            if (Space.RayCast(ray, rayCastFilter, out result))
            {
                hitData = new RayTestData();
                hitData.Location = result.HitData.Location;
                hitData.Normal = result.HitData.Normal;
                hitData.T = result.HitData.T;
                hitData.Tag = result.HitObject.Tag;
                hitData.PhysicObject = result.HitObject;
                return true;
            }
            hitData = new RayTestData();
            return false;
        }

        public bool RayTest(Ray ray, float length, out RayTestData hitData)
        {
            RayCastResult result;
            if (Space.RayCast(ray, length, rayCastFilter, out result))
            {
                hitData = new RayTestData();
                hitData.Location = result.HitData.Location;
                hitData.Normal = result.HitData.Normal;
                hitData.T = result.HitData.T;
                hitData.Tag = result.HitObject.Tag;
                hitData.PhysicObject = result.HitObject;
                return true;
            }
            hitData = new RayTestData();
            return false;
        }
        Func<BroadPhaseEntry, bool> rayCastFilter;
        Func<BroadPhaseEntry, bool> rayCastExcludeFilter;
        Func<BroadPhaseEntry, bool> rayCastExcludesFilter;
        bool RayCastFilter(BroadPhaseEntry entry)
        {
            return entry.CollisionRules.Personal <= CollisionRule.Normal;
        }
        bool RayCastExcludeFilder(BroadPhaseEntry entry)
        {
            return entry != (exclude.PhysicsObject.Tag as Entity).CollisionInformation && (entry.CollisionRules.Personal <= CollisionRule.Normal);
        }
        bool RayCastExcludesFilder(BroadPhaseEntry entry)
        {
            bool ret = false;
            for (int i = 0; i < toExclude.Length; i++)
            {
                WorldObject wo = toExclude[i];
                Entity entity = wo.PhysicsObject.Tag as Entity;
                ret |= entry != (exclude.PhysicsObject.Tag as Entity).CollisionInformation;
            }
            return ret && (entry.CollisionRules.Personal <= CollisionRule.Normal);
        }

        WorldObject[] toExclude;
        WorldObject exclude;
        

        public bool RayTest(Ray ray, float length, out RayTestData hitData, WorldObject exclude)
        {
            RayCastResult result;
            this.exclude = exclude;
            if (Space.RayCast(ray, length, rayCastExcludeFilter, out result))
            {
                hitData = new RayTestData();
                hitData.Location = result.HitData.Location;
                hitData.Normal = result.HitData.Normal;
                hitData.T = result.HitData.T;
                hitData.Tag = result.HitObject.Tag;
                hitData.PhysicObject = result.HitObject;
                exclude = null;
                return true;
            }
            exclude = null;
            hitData = new RayTestData();
            return false;
        }

        public bool RayTest(Ray ray, float length, out RayTestData hitData, params WorldObject[] exclude)
        {
            RayCastResult result;
            toExclude = exclude;
            if (Space.RayCast(ray, length, rayCastExcludesFilter, out result))
            {
                hitData = new RayTestData();
                hitData.Location = result.HitData.Location;
                hitData.Normal = result.HitData.Normal;
                hitData.T = result.HitData.T;
                hitData.Tag = result.HitObject.Tag;
                hitData.PhysicObject = result.HitObject;
                toExclude = null;
                return true;
            }
            toExclude = null;
            hitData = new RayTestData();
            return false;
        }


        public void DeserializedAll()
        {
            throw new NotImplementedException();
        }
    }
}
